import numpy as np
import pandas as pd

# 一、数据文件读写
# 以字典方式初始化DataFrame
df = pd.DataFrame(
    {"id": [1001, 1002, 1003, 1004, 1005, 1009],
     "date": pd.date_range('20130102', periods=6),
     "city": ['Beijing ', 'SH', ' guangzhou ', 'Shenzhen', 'shanghai', 'BEIJING '],
     "age": [23, 44, 54, 32, 34, 32],
     "category": ['100-A', '100-B', '110-A', '110-C', '210-A', '130-F'],
     "price": [1200, np.nan, 2133, 5433, np.nan, 4432]},
    columns=['id', 'date', 'city', 'category', 'age', 'price'])

# 保存DataFrame为csv文件
df.to_csv('data.csv', index=None)

# 读取csv文件的数据到DataFrame
df = pd.read_csv('data.csv')

print(df)

# 二、数据表信息查看
# 1、维度查看：
print(df.shape)

# 2、数据表基本信息（维度、列名称、数据格式、所占空间等）：
df.info()

# 3、每一列数据的格式：
print(df.dtypes)

# 4、某一列格式：
print(df['price'].dtype)

# 5、空值：
tmp_df = df.isnull()

# 6、查看某一列空值：
tmp_df = df['price'].isnull()

# 7、查看某一列的唯一值：
age_array = df['age'].unique()

# 8、查看数据表的值：
values_array = df.values

# 9、查看列名称：
columns_array = df.columns.values

# 10、查看前10行数据、后10行数据：
df.head(3)  # 默认前10行数据
df.tail(3)  # 默认后10 行数据

# 三、数据表清洗
# 1、用数字0填充空值：
df = df.fillna(value=0)  # 注意赋值

# 2、使用列prince的均值对NA进行填充：
df = pd.read_csv('data.csv')  # 读取csv文件的数据到DataFrame
df = df.fillna({'price': df['price'].mean()})

# 3、使用前/后面的值填充缺失部分
df = pd.read_csv('data.csv')  # 读取csv文件的数据到DataFrame
df = df.fillna(method='ffill')
df = pd.read_csv('data.csv')  # 读取csv文件的数据到DataFrame
df = df.fillna(method='bfill')

# 4、清除city字段的字符空格：
df['city'] = df['city'].map(str.strip)

# 5、大小写转换：
df['city'] = df['city'].str.lower()

# 6、更改数据格式：
df.astype({'price': 'int'})

# 7、更改列名称：
df.rename(columns={'category': 'category-size'}, inplace=True)

# 8、删除后出现的重复值：
df['city'] = df['city'].drop_duplicates()

# 9、删除先出现的重复值：
df = df.fillna(method='ffill')  # 为了演示效果先填充回去
df['city'] = df['city'].drop_duplicates(keep='last')
df = df.fillna(method='bfill')  # 为了演示效果再次填充回去

# 10、数据替换：
df['city'] = df['city'].replace('sh', 'shanghai')

# 四、数据预处理
df1 = pd.DataFrame(
    {"id": [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
     "gender": ['male', 'female', 'male', 'female', 'male', 'female', 'male', 'female'],
     "pay": ['Y', 'N', 'Y', 'Y', 'N', 'Y', 'N', 'Y', ],
     "m-point": [10, 12, 20, 40, 40, 40, 30, 20]})

# 1、数据表合并
# 1.1 merge
# 可用于两个df间行方向（一般用join代替）或列方向的拼接操作，默认列拼接，取交集（即：存在相同主键的df1和df2的列拼接）
# 提供了类似于SQL数据库连接操作的功能，支持左连接、右连接、内连接和外连接等全部四种SQL连接操作类型
df_inner = pd.merge(df, df1, how='inner')  # 匹配合并，交集
df_left = pd.merge(df, df1, how='left')
df_right = pd.merge(df, df1, how='right')
df_outer = pd.merge(df, df1, how='outer')  # 外连接

# 1.2 append
# 可用于df间行方向的拼接操作，默认
result = df.append(df1)

# 1.3 join
# 可用于df间列方向的拼接操作，默认左列拼接，支持左联、右联、内联和外联四种操作类型
df.set_index(['id'], inplace=True)
df1.set_index(['id'], inplace=True)
result = df.join(df1, on='id')

# 1.4 concat
# 可用于两个或多个df间行方向（增加行，下同）或列方向（增加列，下同）进行内联或外联拼接操作，默认行拼接，取并集
# 提供了参数axis设置行/列拼接的方向
#
# pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
#           keys=None, levels=None, names=None, verify_integrity=False,
#           copy=True)
# 
# objs︰ 一个序列或系列、 综合或面板对象的映射。如果字典中传递，将作为键参数，使用排序的键，除非它传递，
#       在这种情况下的值将会选择 （见下文）。任何没有任何反对将默默地被丢弃，除非他们都没有在这种情况下将引发 ValueError。
# axis: {0，1，…}，默认值为 0。要连接沿轴。
# join: {'内部'、 '外'}，默认 '外'。如何处理其他 axis(es) 上的索引。联盟内、 外的交叉口。
# ignore_index︰ 布尔值、 默认 False。如果为 True，则不要串联轴上使用的索引值。由此产生的轴将标记 0，…，n-1。
#              这是有用的如果你串联串联轴没有有意义的索引信息的对象。请注意在联接中仍然受到尊重的其他轴上的索引值。
# join_axes︰ 索引对象的列表。具体的指标，用于其他 n-1 轴而不是执行内部/外部设置逻辑。
# keys︰ 序列，默认为无。构建分层索引使用通过的键作为最外面的级别。如果多个级别获得通过，应包含元组。
# levels︰ 列表的序列，默认为无。具体水平 （唯一值） 用于构建多重。否则，他们将推断钥匙。
# names︰ 列表中，默认为无。由此产生的分层索引中的级的名称。
# verify_integrity︰ 布尔值、 默认 False。检查是否新的串联的轴包含重复项。这可以是相对于实际数据串联非常昂贵。
# 副本︰ 布尔值、 默认 True。如果为 False，请不要，不必要地复制数据。
df_1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                     'B': ['B0', 'B1', 'B2', 'B3'],
                     'C': ['C0', 'C1', 'C2', 'C3'],
                     'D': ['D0', 'D1', 'D2', 'D3']},
                    index=[0, 1, 2, 3])

df_2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
                     'B': ['B4', 'B5', 'B6', 'B7'],
                     'C': ['C4', 'C5', 'C6', 'C7'],
                     'D': ['D4', 'D5', 'D6', 'D7']},
                    index=[4, 5, 6, 7])

df_3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                     'B': ['B8', 'B9', 'B10', 'B11'],
                     'C': ['C8', 'C9', 'C10', 'C11'],
                     'D': ['D8', 'D9', 'D10', 'D11']},
                    index=[8, 9, 10, 11])

df_4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'], 'D': ['D2', 'D3', 'D6', 'D7'],
                     'F': ['F2', 'F3', 'F6', 'F7']}, index=[2, 3, 6, 7])

# 列名（columns）相同，行索引（index）无重复项的表df1、df2、df3实现行拼接
frames = [df_1, df_2, df_3]
# 使用参数key可以为每个数据集指定块标记
result = pd.concat(frames, keys=['x', 'y', 'z'])

# 使用concat()实现df_1、df_4行拼接
result = pd.concat([df_1, df_4], sort=False)

# 使用concat()实现df_1、df_4列拼接
result = pd.concat([df_1, df_4], axis=1)

# concat修改join='inner'，只保留重复列索引的行拼接
result = pd.concat([df_1, df_4], join='inner')

# concat修改join='inner'，只保留重复行索引的列拼接
result = pd.concat([df_1, df_4], axis=1, join='inner')

# 利用参数join_axes=[df_1.index]指定concat按照df_1的行索引进行列拼接，df2仅保留行索引与df_1重复的部分
result = pd.concat([df_1, df_4], axis=1, join_axes=[df_1.index])

# 2、设置索引列
df_inner.set_index('id')

# 3、按照特定列的值排序, ascending=True 升序，ascending=False 降序
df_inner.sort_values(by=['age'], inplace=True, ascending=False)

# 4、按照索引列排序：
df_inner.sort_index(ascending=False, inplace=True)

# 5、如果prince列的值>3000，group列显示high，否则显示low：
df_inner['group'] = np.where(df_inner['price'] > 3000, 'high', 'low')

# 6、对复合多个条件的数据进行分组标记
df_inner.loc[(df_inner['city'] == 'beijing') & (df_inner['price'] >= 1000), 'sign'] = 1

# 7、对category字段的值依次进行分列，并创建数据表，索引值为df_inner的索引列，列名称为category和size
split = pd.DataFrame((x.split('-') for x in df_inner['category-size']),
                     index=df_inner.index,
                     columns=['category', 'size'])

# 8、将完成分裂后的数据表和原df_inner数据表进行匹配
df_inner = pd.merge(df_inner, split, right_index=True, left_index=True)

# 五、数据提取
# 主要用到的三个函数：loc,iloc和ix，loc函数按标签值进行提取，iloc按位置进行提取，ix可以同时按标签和位置进行提取。

# 1、按索引提取单行的数值, 返回的是Series
row = df_inner.loc[3]

# 2、按索引提取区域行数值，返回的是DataFrame
rows = df_inner.iloc[0:3]

# 3、重设索引
df_inner.reset_index()

# 4、设置日期为索引
df_inner = df_inner.set_index('date')

# 5、提取4日之前的所有数据
tmp = df_inner[:'2013-01-04']

# 6、使用iloc按位置区域提取数据
tmp = df_inner.iloc[:3, :2]
# 冒号前后的数字不再是索引的标签名称，而是数据所在的位置，从0开始，前三行，前两列

# 7、适应iloc按位置单独提起数据
tmp = df_inner.iloc[[0, 2, 4], [4, 5]]  # 提取第0、2、4行，4、5列

# 8、使用ix按索引标签和位置混合提取数据
tmp = df_inner.ix[:'2013-01-03', :4]  # 2013-01-03号之前，前四列数据

# 9、判断city列的值是否为北京
tmp = df_inner['city'].isin(['beijing'])

# 10、判断city列里是否包含beijing和shanghai，然后将符合条件的数据提取出来
tmp = df_inner.loc[df_inner['city'].isin(['beijing', 'shanghai'])]

# 11、提取前三个字符，并生成数据表
tmp = pd.DataFrame(df_inner['category'].str[:3])

# 六、数据筛选
# 使用与、或、非三个条件配合大于、小于、等于对数据进行筛选，并进行计数和求和。

# 1、使用"与"进行筛选
tmp = df_inner.loc[(df_inner['age'] > 35) & (df_inner['city'] == 'shanghai'),
                   ['id', 'city', 'age', 'category', 'gender']]

# 2、使用"或"进行筛选，注意在新版本中，旧版本的sort函数已经被sort_values函数所代替了
tmp = df_inner.loc[
    (df_inner['age'] > 40) | (df_inner['city'] == 'beijing'),
    ['id', 'city', 'age', 'category', 'gender']].sort_values(['age'])

# 3、使用"非"条件进行筛选
tmp = df_inner.loc[(df_inner['city'] != 'beijing'),
                   ['id', 'city', 'age', 'category', 'gender']].sort_values(['id'])

# 4、对筛选后的数据按city列进行计数
tmp = df_inner.loc[(df_inner['city'] != 'beijing'),
                   ['id', 'city', 'age', 'category', 'gender']].sort_values(['id']).city.count()

# 5、使用query函数进行筛选
tmp = df_inner.query('city == ["beijing", "shanghai"]')

# 6、对筛选后的结果按prince进行求和
tmp = df_inner.query('city == ["beijing", "shanghai"]').price.sum()

# 七、数据汇总
# 主要函数是groupby和pivote_table

# 1、对所有的列进行计数汇总
tmp = df_inner.groupby('city').count()

# 2、按城市对id字段进行计数
tmp = df_inner.groupby('city')['id'].count()

# 3、对两个字段进行汇总计数
tmp = df_inner.groupby(['city', 'size'])['id'].count()

# 4、对city字段进行汇总，并分别计算prince的合计和均值
tmp = df_inner.groupby('city')['price'].agg([len, np.sum, np.mean])

# 八、数据统计
# 数据采样，计算标准差，协方差和相关系数
# 1、简单的数据采样
tmp = df_inner.sample(n=3)

# 2、手动设置采样权重
weights = [0, 0, 0, 0.5, 0.5]
tmp = df_inner.sample(n=2, weights=weights)

# 3、采样后不放回
tmp = df_inner.sample(n=5, replace=False)

# 4、采样后放回
tmp = df_inner.sample(n=5, replace=True)

# 5、 数据表描述性统计
tmp = df_inner.describe().round(2).T  # round函数设置显示小数位，T表示转置

# 6、计算列的标准差
tmp = df_inner['price'].std()

# 7、计算两个字段间的协方差
tmp = df_inner['price'].cov(df_inner['m-point'])

# 8、数据表中所有字段间的协方差
tmp = df_inner.cov()

# 9、两个字段的相关性分析
tmp = df_inner['price'].corr(df_inner['m-point'])
# 相关系数在-1到1之间，接近1为正相关，接近-1为负相关，0为不相关

# 10、数据表的相关性分析
tmp = df_inner.corr()

# 九、数据输出
# 分析后的数据可以输出为xlsx格式和csv格式
# 1、写入Excel
df_inner.to_excel('result.xlsx', sheet_name='result')

# 2、写入到CSV
df_inner.to_csv('result.csv')
